import random
import sys
import gc

from imports.cell import *
from imports.binaryboard import *
from imports.forbiddenlist import *
from imports.enforcelist import *
from imports.testboard import *

class ComplexBoard:
    def __init__(self, rows, columns):
        self.rows = rows
        self.columns = columns
        self.cell = []
    def get_solution(self):
        board = BinaryBoard(self.rows, self.columns)
        board.renew()
        self.solution = set(board.solution)
        del board
        gc.collect()
    def build(self):
        n = False
        while not n:
            n = self.renew()
        if not TestBoard(self).is_valid():
            self.build()
    def renew(self):
        self.cell = []
        # for i in range(self.rows):
            # for j in range(self.columns):
                # self.cell.append(Cell(0,i,j))
        for i in range(self.rows*self.columns):
            self.cell.append(Cell(0))
        self.get_solution()
        for i in self.solution:
            self.cell[i].state = 1
        self.forbidden_list = ForbiddenList(self.rows,self.columns)
        self.enforce_list = EnforceList(self.rows, self.columns)
        self.get_unmarked_list()
        return self.fill()
    def forbid(self, index, value):
        self.forbidden_list.remove(index, value)
        self.unenforce_row(index, value)
        self.unenforce_column(index, value)
    def unenforce_row(self, index, value):
        row = self.enforce_list.row_of(index)
        target = self.enforce_list.row_list[row]
        if value in target:
            for i in range(self.columns):
                dest = row*self.columns + i
                if self.cell[dest].value == value:
                    if self.cell[dest].state == 1:
                        self.enforce_list.remove_column(dest, value)
            self.enforce_list.remove_row(index, value)
    def unenforce_column(self, index, value):
        column = self.enforce_list.column_of(index)
        target = self.enforce_list.column_list[column]
        if value in target:
            for i in range(self.rows):
                dest = self.columns*i + column
                if self.cell[dest].value == value:
                    if self.cell[dest].state == 1:
                        self.enforce_list.remove_row(dest, value)
            self.enforce_list.remove_column(index, value)
    def assign_unmarked(self, index):
        set = self.forbidden_list.get_possibility(index)
        if len(set) == 0:
            return False
        # while set != {}:
            # set.pop()
        self.cell[index].value = set.pop()
        self.forbid(index, self.cell[index].value)
        self.update_unmarked(index)
        return True
    def assign_marked(self, index):
        max_value = max(self.rows, self.columns)
        random.seed()
        value = random.randint(1, max_value)
        self.cell[index].value = value
        if self.forbidden_list.is_in_row(index, value):
            return
        if self.forbidden_list.is_in_column(index, value):
            return
        # breakpoint()
        self.enforce_list.add(index, value)
    def assign_enforced(self, index):
        i = self.forbidden_list.column_of(index)
        possibilities = self.forbidden_list.get_possibility(index).intersection(set(self.enforce_list.column_list[i]))
        if len(possibilities) == 0:
            self.cell[index].value = self.enforce_list.column_list[i].pop()
            options = []
            while i < index:
                if self.cell[i].state == 0:
                    if not self.has_same_in_row(i):
                        if self.forbidden_list.is_valid(i, self.cell[index].value):
                            if self.forbidden_list.is_valid(index, self.cell[i].value):
                                options.append(i)
                i += self.columns
            if len(options) == 0:
                self.cell[index].value = 0
                return False
            else:
                self.swap_in_column(options.pop(), index)
        else:
            self.cell[index].value = possibilities.pop()
            self.forbid(index, self.cell[index].value)
            self.update_unmarked(index)
        return True
    def assign_last_enforced(self, index):
        # enforces = set(self.enforce_list.column_list[self.forbidden_list.column_of(index)]).union(set(self.enforce_list.row_list[self.forbidden_list.row_of(index)]))
        i = self.forbidden_list.row_of(index)
        possibilities = self.forbidden_list.get_possibility(index).intersection(set(self.enforce_list.row_list[i]))
        if len(possibilities) == 0:
            # breakpoint()
            self.cell[index].value = self.enforce_list.row_list[i].pop()
            options = []
            for j in range(i*self.columns, self.rows*self.columns):
                if self.cell[j].state == 0:
                    if not self.has_same_in_column(j):
                        if self.forbidden_list.is_valid(j, self.cell[index].value):
                            options.append(j)
            if len(options) == 0:
                return False
            else:
                self.swap_in_row(options.pop(), index)
        else:
            self.cell[index].value = possibilities.pop()
            self.forbid(index, self.cell[index].value)
            self.update_unmarked(index)
            
    def swap_in_column(self, index1, index2):
        temp = self.cell[index1].value
        self.forbidden_list.add_row(index1, temp)
        self.cell[index1].value = self.cell[index2].value
        self.forbidden_list.remove_row(index1, self.cell[index1].value)
        self.forbidden_list.add_row(index2, self.cell[index2].value)
        self.cell[index2].value = temp
        self.forbidden_list.remove_row(index2, self.cell[index2].value)
        
    def swap_in_row(self, index1, index2):
        temp = self.cell[index1].value
        self.forbidden_list.add_column(index1, temp)
        self.cell[index1].value = self.cell[index2].value
        self.forbidden_list.remove_column(index1, self.cell[index1].value)
        self.forbidden_list.add_column(index2, self.cell[index2].value)
        self.cell[index2].value = temp
        self.forbidden_list.remove_column(index2, self.cell[index2].value)
        
    # def is_row_dependent(self, index):
        # value = self.cell[index].value
        # if self.value
    # def assign_marked(self, cell):
        # max_value = max(self.rows, self.columns)
        # cell.value = random.randint(1, max_value)
        # if not self.has_same_in_row(cell) AND not self.has_same_in_column(cell):
            # cell.mood = 0
            # if random.randint(0,1) == 0:
                # self.value = 0
    def fill(self):
        for i in range(self.rows):
            for j in range(self.columns):
                random.seed()
                index = i*self.columns + j
                if self.cell[index].state == 1:
                    self.assign_marked(index)
                else:
                    remaining_enforces = len(self.enforce_list.column_list[j])
                    if remaining_enforces >= self.unmarked_list[1][j]:
                        if not self.assign_enforced(index):
                            return False
                    else:
                        if i == self.rows - 1:
                            remaining_enforces = len(self.enforce_list.row_list[i])
                            if remaining_enforces >= self.unmarked_list[1][j]:
                                if not self.assign_last_enforced(index):
                                    return False
                                    
                        if not self.assign_unmarked(index):
                            return False
                # breakpoint()
                # print(self)
                # print(self.forbidden_list.column_list)
                # print(self.enforce_list.column_list)
                # print(self.forbidden_list.row_list)
                # print(self.enforce_list.row_list)
                # breakpoint()
        # last_row = self.rows - 1
        # for j in range(self.columns):
            # random.seed()
            # index = i*self.columns + j
            # if self.cell[index].state == 1:
                # self.assign_marked(index)
            # else:
                # remaining_enforces = len(self.enforce_list.row_list[last_row])
                # if remaining_enforces >= self.unmarked_list[0][last_row]
                    # if not self.assign_enforced(index):
                        # return False
                    # else:
                        # if not self.assign_unmarked(index):
                            # return False
        # breakpoint()
        if self.has_remaining_enforces():
            return False
        return True
    def has_remaining_enforces(self):
        for i in self.enforce_list.column_list:
            if len(i) != 0:
                return True
        for i in self.enforce_list.row_list:
            if len(i) != 0:
                return True
        return False
    def has_same_in_column(self, index):
        i = self.forbidden_list.column_of(index)
        ct = 0
        while i < len(self.cell):
            if self.cell[i].value == self.cell[index].value:
                ct += 1
                if ct == 2:
                    return True
            i += self.columns
        return False
    def has_same_in_row(self, index):
        i = self.forbidden_list.row_of(index) * self.columns
        ct = 0
        for j in range(self.columns):
            if self.cell[i+j].value == self.cell[index].value:
                ct += 1
                if ct == 2:
                    return True
        return False
        
    def get_unmarked_list(self):
        self.unmarked_list = [[],[]]
        for i in range(self.rows):
            self.unmarked_list[0].append(self.columns)
        for i in range(self.columns):
            self.unmarked_list[1].append(self.rows)
        for i in self.solution:
            self.update_unmarked(i)
    def update_unmarked(self, index):
        self.unmarked_list[0][self.forbidden_list.row_of(index)] -= 1
        self.unmarked_list[1][self.forbidden_list.column_of(index)] -= 1
    
    def unfinished(self):
        for i in self.cell:
            if i.value == 0:
                return True
        return False
    def __str__(self):
        if len(self.cell) == 0:
            return ""
        string = ""
        for i in range(self.rows):
            for j in range(self.columns):
                string += str(self.cell[i*self.columns+j]) + ' '
            string = string[:-1] + '\n'
        return string[:-1]